上一篇我們先把 MFA、OTP、TOTP 與 WebAuthn / Passkey 講完,重點放在「使用者要怎麼證明自己是本人」。
但登入驗證成功之後,系統還有另一個問題要處理:HTTP 本身是無狀態(stateless)的協定。也就是說,伺服器不會天生記得上一個請求是誰送來的。
如果每次操作都要重新輸入密碼、重新跑一次 MFA,使用者體驗一定會很差。所以今天要來看幾種常見的 HTTP 驗證機制:它們分別用什麼方式,讓每一次 Request 都能帶著某種「身份憑證」。
今天內容涵蓋:
工程師平常寫程式、呼叫 API 時,會遇到一個很基礎的問題:一支 API 要怎麼知道「誰在呼叫我」?
常見做法其實可以先分成兩大陣營:
| 陣營 | 包含的方式 | 共同特色 |
|---|---|---|
| 傳統 HTTP 驗證方式 | Basic Auth、Digest Auth、API Key、Session | 驗證資訊(帳密、識別碼、或伺服器端記錄的編號)本身就是「憑證」,每次請求附上就好 |
| Token-Based Auth | Bearer & JWT Token、Access & Refresh Token | 先驗證一次,換到一份可獨立驗證真偽的「憑證」,之後靠這份憑證代表身份 |
今天先把傳統 HTTP 驗證方式這一陣營拆開來看。Token-Based Auth 底下的 Bearer、JWT Token、Access Token、Refresh Token,內容比較多,留到下一篇一次講清楚。
工程師要呼叫公司一支很舊的內部維運工具 API,這支工具沒有登入頁面、也沒有 Session,最基礎的驗證方式就是 Basic Auth。

做法很直接:把「帳號:密碼」用冒號接起來,整串用 Base64 編碼,放進請求的 Authorization header 送出去。
流程如下:
401 Unauthorized,並在 WWW-Authenticate: Basic realm="..." 裡告知需要用 Basic Auth。帳號:密碼 做 Base64 編碼,放進 Authorization: Basic <編碼結果> 重新發送請求。例如帳號 gloria、密碼 mysecret,送出的 header 會長這樣:
Authorization: Basic Z2xvcmlhOm15c2VjcmV0
風險:Base64 只是編碼,不是加密,任何人攔截到封包都能直接還原出帳密明文。所以 Basic Auth 一定要搭配 HTTPS 使用,而且 HTTP 本身沒有「登出」的概念,只能靠瀏覽器自己清掉快取的帳密。
如果不想讓密碼(哪怕只是編碼過的)直接出現在封包裡,可以改用 Digest Auth。它用的是「challenge-response」的方式,全程只傳送密碼算出來的雜湊值,密碼本身從頭到尾不會上網路。

流程如下:
401,並在 WWW-Authenticate: Digest 裡附上 realm、一次性亂數 nonce 等參數。nonce 等資訊算出一段摘要,放進 Authorization: Digest 裡重新送出。摘要用的雜湊演算法可以用 algorithm 參數指定:舊版本默認是 MD5,但現行標準 RFC 7616 已新增支援 SHA-256、SHA-512-256,並明確標示 MD5 「NOT RECOMMENDED」,實作時改用 SHA-256 會更安全。
因為 nonce 每次都不一樣,就算封包被攔截,攻擊者也無法直接拿去重放使用。不過因為實作複雜,Digest Auth 在一般 Web 應用已經很少見,比較常出現在網路電話(SIP)之類的場景。
工程師的團隊要串接一個地圖服務(例如 Google Maps),這類服務通常不需要知道「是哪個使用者」,只需要知道「是哪個應用程式/專案在呼叫」,這時候用的就是 API Key。

API Key 是服務提供者發給你的一串識別碼,可以放在三個地方:
Authorization: Bearer <api_key>(這裡的 Bearer 是驗證方案名稱,細節下一篇再展開),或自訂欄位 X-API-Key: <api_key>
?api_key=<api_key>
跟 Basic Auth 的差別:Basic Auth 驗證的通常是「這個人是誰」,API Key 驗證的通常是「這個應用程式/專案是誰」,很適合 server-to-server、公開資料 API、地圖服務這類場景,也常被拿來做流量控管與計費依據。
風險:API Key 通常長期有效,而且本身沒有內建的過期機制,除非自己額外實作,否則一旦外洩,任何拿到這把 Key 的人都能以你的名義持續存取資源,很難追查是誰在用,所以多半只用來存取公開或非機密的資料;真的牽涉到機密資料,就得換成更嚴謹的使用者驗證方式。
跟 JWT 的本質差異:API Key 只是一串隨機字串,本身不帶任何資訊,伺服器想知道這把 Key 屬於誰、有什麼權限,都得去查資料庫;而下一篇會提到的 JWT,則是把身份資訊直接編碼進 Token 裡,伺服器不需要查資料庫就能驗證。
使用者帳號通過密碼與 MFA 之後,系統知道「這確實是本人」。但 HTTP 本身是無狀態(stateless)的協定——伺服器不會自動記得上一個請求是誰送來的。如果每次操作都要重新輸入密碼、重新跑一次 MFA,會非常麻煩,使用者體驗也會不佳。

Session-based 登入的做法是:登入成功後,伺服器在自己這邊建立一筆記錄,叫做 Session,裡面記著「這是哪個帳號、什麼時候登入的、登入到什麼時候過期」。伺服器接著把這筆記錄的編號(Session ID)交給瀏覽器,通常存在一個 Cookie 裡。
之後使用者每發出一次請求,瀏覽器都會自動帶上這個 Cookie。伺服器拿到 Session ID,就去自己的資料庫或記憶體裡查:「這個編號對應的是哪個 Session?還沒過期嗎?」查得到而且沒過期,就當作這個人還是登入狀態。
可以把這個機制想成寄物櫃:你進門的時候把外套交給服務生(登入),服務生把外套掛好,只給你一張號碼牌(Session ID)。你身上什麼資訊都不用帶,只要拿著號碼牌,服務生看到號碼就知道要拿哪件外套給你。
Session-based 登入的特性:
風險:Session Fixation(session 固定攻擊)
OWASP 目前的 Session Management 安全指南中,仍將這種攻擊列為重點風險之一:攻擊者先想辦法讓受害者的瀏覽器帶著一組「攻擊者自己事先指定好」的 Session ID(例如透過惡意連結或埋在連結裡的參數),受害者用這組 ID 登入成功後,伺服器就把這組 ID 跟受害者的身份綁在一起,攻擊者接著直接拿同一組 ID 就能冒充受害者,根本不需要偷密碼。
目前業界公認的標準做法是:使用者登入成功後,伺服器要重新產生一組全新的 Session ID,而不是沿用登入前就已經存在的舊 ID。常見後端框架如 PHP 的 session_regenerate_id()、Laravel 的 session()->regenerate(),都有內建方法可以直接呼叫。
很多人會把兩者混為一談,其實它們扮演的角色完全不同:
| 比較項目 | Session | Cookie |
|---|---|---|
| 存放位置 | 伺服器端 | 使用者的瀏覽器 |
| 本質 | 伺服器保存的一筆登入紀錄(資料) | 瀏覽器的儲存機制,什麼資料都能放 |
| 裡面裝的是什麼 | 使用者身份、登入時間、過期時間等實際資料 | 在 Session-based 登入裡,通常只放 Session ID 這組指標 |
| 誰能讓它失效 | 伺服器隨時可以刪除,效果立即生效 | 由瀏覽器依 Expires/Max-Age 管理,伺服器刪不到使用者本機的 Cookie |
簡單說:Session 是伺服器手上那份記錄,Cookie 只是把「找到這份記錄的鑰匙(Session ID)」放在使用者瀏覽器裡的其中一種容器——Cookie 本身不是 Session,也不一定要拿來裝 Session ID。
這種「伺服器記住你」的做法,跟下一篇要介紹的 Token-Based Auth「你自己帶著身份走」剛好是兩種相反的思路,兩者的完整比較也留到下一篇。
| 概念 | 說明 |
|---|---|
| Basic Auth | 帳密 Base64 編碼直接附在請求上,簡單但沒有加密 |
| Digest Auth | 用 challenge-response 傳送密碼算出的摘要,避免密碼明文出現在網路上 |
| API Key | 代表應用程式身份的識別碼,常用在 server-to-server、公開 API 或計費控管 |
| Session-based 登入 | 伺服器記住登入狀態,使用者只帶著一組 Session ID |
| Cookie | 瀏覽器端的儲存機制,在 Session-based 登入裡常用來保存 Session ID |
今天把傳統 HTTP 驗證方式(Basic Auth、Digest Auth、API Key、Session-based)講完了,但故事還沒結束——Token-Based Auth 底下其實藏著 Bearer、JWT Token、Access Token、Refresh Token 好幾個要分開釐清的概念,還有它跟 Session-based 之間的完整比較,這些就留到下一篇一次講清楚。